# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
Class that creates a prolog representation of a concept map in an independent
representation (a class of type Graph).

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


import re

from log import log
from pyswip import Variable
log.debug('Loaded...')


def shouldQuote(value):
    '''Returns True if the value should be quoted'''
    try:
        value.encode('ascii')
    except UnicodeError:
        return True
    return ' ' in value or \
           not value[0].islower() or \
           value[0] == '_' or \
           '.' in value or \
           ',' in value or \
           ';' in value or \
           ':' in value or \
           '&' in value or \
           '/' in value or \
           '\\' in value or \
           '$' in value or \
           '#' in value or \
           '@' in value or \
           '!' in value or \
           '*' in value or \
           '*' in value or \
           '+' in value or \
           '?' in value or \
           '<' in value or \
           '>' in value or \
           '{' in value or \
           '}' in value or \
           '[' in value or \
           ']' in value or \
           '(' in value or \
           ')' in value or \
           '=' in value or \
           '|' in value
    

def assureQuotes(atom):
    '''Assure that atom is correctly quoted for swip'''
    
    if None == atom or isinstance(atom, Variable):
        return ''
    if isinstance(atom, (list, tuple, set)):
        return '[%s]' % ','.join(assureQuotes(el) for el in atom)
    if isinstance(atom, basestring) and len(atom) > 0:
        if len(atom) == 0:
            return ''
        if "'" == atom[0]:
            return atom
        if shouldQuote(atom):
            return "'%s'" % atom
        else:
            return atom
    else:
        print atom
        raise Exception('Unexpected type: ' + str(type(atom)))
    
def assemblyQuery(query, *args, **kargs):
    '''Creates a query quoting the elements according to what swipl expects'''
    if kargs == {}:
        if args == ():
            return query
        elif isinstance(args[0], dict):
            kargs = args[0]
        else:
            args = tuple([assureQuotes(arg) for arg in args])
            return query % args
        
    args = dict([(key, assureQuotes(arg)) for key, arg in kargs.iteritems()])
    return query % args
    
def assemblyText(text, entities, db):
    '''Creates a text according to what a human being expects.
        It uses the informed db to get the names of the informed concepts.
    '''
    
    # Algorithm:
    # 1) First gather from text all items that must be changed
    # 2) Them identify in the dictionary entities that are lists
    query = 'label(%s, Label)'
    regex = re.compile('%\(([a-zA-Z1-9]*)\)s')
    
    quote = lambda text: text.replace('&', '&amp;') \
                             .replace('<', '&lt;') \
                             .replace('>', '&gt;')
    
    matches = regex.findall(text)
    labels = {}
    try:
        for match in matches:
            entity = entities[match]
            if isinstance(entity, (list, tuple, set)):
                result = []
                for item in entity:
                    queryResult = list(db.query(assemblyQuery(query, item))) 
                    result.append(quote(queryResult[0]['Label']))
                
                labels[match] = ', '.join(result) if len(result) > 1 \
                                                  else result[0]
            else:
                newQuery = assemblyQuery(query, entity)
                queryResult = list(db.query(newQuery))
                if len(queryResult) > 0:
                    labels[match] = quote(queryResult[0]['Label'])
                else:
                    labels[match] = quote(entity)
                
        return text % labels
    except IndexError:
        print('Index error in: ' + match)
        return None

class CMap2Prolog:
    '''Creates a prolog knowledge base from a graph.
   
    In this base, each element (arc or node) is recognized by it id. The label
    can be found by querying the predicate hasLabelCM(id, label).
   
    In the KB, every functor that is related to conceptual maps ends with CM
   
    The KB structure is the following:
       conceptCM(id)
       linkingPhraseCM(id)
       relationCM(R,Cfrom,Cto) % R-linkingPhraseCM, Cfrom and Cto - conceptCM.
       hasLabelCM(id, label)
    '''
   
    def __init__(self, graph):
        self.graph = graph
        self.concepts       = []
        self.linkingPhrases = [] 
        self.relations      = []
        self.labels         = []
        self.llabels        = []
        
        self.setGraph()
    
    def cleanUpWhiteSpace(self, label):
        '''Utility method to clean things such as \\n from the strings'''
        label = label.split('\n')
        label = ' '.join(label)
        label = label.split(' ')
        label = ' '.join([part for part in label if part != ''])
        return label.replace('\\', '\\\\')
        
    
    def setGraph(self):
        "Sets the value of the graph variable and make the necessary analysis"
      
        graph = self.graph
        
        concepts = []
        linkingPhrases = []
        relations = []
        labels = []
        llabels = []
      
        for elId in graph.nodes:
            concept = graph.getElementById(elId)
            
            prologText =  "conceptCM('%s')" % concept.id
            concepts.append(prologText)
            
            prologText = "hasLabelCM('%s','%s')" % (concept.id,
                                  self.cleanUpWhiteSpace(concept.label))
            labels.append(prologText)
            
            prologText = "hasLabelCMlower('%s','%s')" % (concept.id,
                                  self.cleanUpWhiteSpace(concept.label).lower())
            llabels.append(prologText)
      
        for elId in graph.arcs:
            arc = graph.getElementById(elId)
         
            prologText =  "linkingPhraseCM('%s')" % arc.id
            linkingPhrases.append(prologText)
            
            prologText = "hasLabelCM('%s','%s')" % (arc.id,
                                    self.cleanUpWhiteSpace(arc.label))
            labels.append(prologText)
            
            prologText = "hasLabelCMlower('%s','%s')" % (arc.id,
                                  self.cleanUpWhiteSpace(arc.label).lower())
            llabels.append(prologText)
         
            for elFrom in arc.inbound:
                for elTo in arc.outbound:
                    prologText = "relationCM('%s','%s','%s')" % \
                                  (arc.id, elFrom.id, elTo.id)
                    relations.append(prologText)
               
        self.concepts       = concepts
        self.linkingPhrases = linkingPhrases
        self.relations      = relations
        self.labels         = labels
        self.llabels        = llabels
      
    def createKBinEngine(self, engine):
        '''Instantiate each fact of the KB in a Prolog engine.'''
      
        for concept in self.concepts:
#            print concept
            engine.assertz(concept)
        for relation in self.relations:
#            print relation
            engine.assertz(relation)
        for linkingPhrase in self.linkingPhrases:
#            print linkingPhrase
            engine.assertz(linkingPhrase)
        for label in self.labels:
#            print label
            engine.assertz(label)
        for llabel in self.llabels:
#            print label
            engine.assertz(llabel)
            
#        print self.__str__()
      
      
    def __str__(self):
        return '\n'.join(self.concepts) + '\n\n' + \
               '\n'.join(self.linkingPhrases) + '\n\n' + \
               '\n'.join(self.relations) + '\n\n' + \
               '\n'.join(self.labels) + '\n\n' + \
               '\n'.join(self.llabels)
   
   
   
if __name__ == '__main__':
   
    from CXLReader import CXLReader
   
    p = CXLReader('test/Mapa simples.cxl')
    p.parse()
   
    kb = CMap2Prolog(p.getData())
   
    print kb

   
   
